$j = jQuery.noConflict();
/**
 * /!\ Attention: 
 * D'après un premier constat il semble que les CHECKBOX ne fonctionnent pas avec JTABLE. En gros la valeur d'une checkbox n'est pas envoyée par post lors d'une modification.
 * Mais je peux me tromper et simplement avoir mal utilisé le système. 
 * 
 * http://www.jtable.org/ApiReference#fopt-type
 */

/**
 * @name global.js
 * @desc Contain all function javascript.
 * 
 * ***************************** General functionalities *****************************
 * @method ajax
 * @method webService
 * @method checkWebServiceResponse
 * @method convertWebServiceDatasToArray
 * @method site_url
 * @method consoleDevelopment
 * @method displayMessage
 * @method hideMessage
 * @method applyPagination
 * @method setLocalStorage
 * @method getLocalStorage
 * @method detectActions
 * @method addLocationHash
 * @method removeLocationHash
 * @method getLocationHash
 * @method autoFillForm
 * 
 * 
 * ***************************** Others functionalities *****************************
 * 
 * @method trim
 * @method ucfirst
 * @method lcfirst
 * @method getCookie
 * @method setCookie
 * @method deleteCookie
 * @method countSubString
 * @method inArray
 * @method replaceString
 * @method checkEmail
 * @method checkPhone
 * @method is_array
 * @method transformCheckboxeToTriStateCheckbox
 * @method loadPage
 * @method detectEnterKeyPressed
 * @method cloneObject
 * @method autoDetectDivToUseJTable
 * @method getTrueUrl
 * @method extractUrlParams
 * @method Sha1
 * 
 * 
 * @date 12/06/2012
 * @lastUpdate 21/11/2012
 */

/**
 * @name CONSTANT & GLOBALS VARS [MUST BE INITIALIZED BEFORE !]
 * 
 * config - array of values: EX: config.baseUrl - Generate by PHP in views/head.php
 * 		baseUrl - string - The PHP base url.
 * 		api_directory - string - The PHP api directory.
 * 		controller_webservice_name - string - The second part of controller name.
 * 		rest_key_name - string - The name of the API KEY, for the WebService.
 * 		webservice_carwego_key - string - The value of the API KEY for Carwego.
 * 		environment - string - The PHP const for the current environment (development/testing/production)
 * 		theme - string - The current theme in use. (Path of the theme).
 * 		controller - string - The name of the current PHP controller.
 * 		method - string - The name of the current PHP method in the current controller.
 * 		
 * 		alert - Array - The key => value of the CSS class can be used for display messages.
 * 			error - Style for display errors.
 * 			block - Style for display warnings.
 * 			info - Style for display infos.
 * 			success - Style for display success.
 * 
 * 
 * $j - object - Acronym for the jQuery Framework. (noConflict enable)
 * hideMessage_setTimeOut - int - Define the time before the function hideMessage must be auto called.
 * loadPage_setTimeOut - int - Define the time before the function loadPage must be auto called.
 */
var hideMessage_setTimeOut;
var loadPage_setTimeOut;


// Config language for jQuery datePicker.
jQuery(function($){
    $j.datepicker.regional['fr'] = {clearText: 'Effacer', clearStatus: '',
        closeText: 'Fermer', closeStatus: 'Fermer sans modifier',
        prevText: '<Préc', prevStatus: 'Voir le mois précédent',
        nextText: 'Suiv>', nextStatus: 'Voir le mois suivant',
        currentText: 'Courant', currentStatus: 'Voir le mois courant',
        monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin',
        'Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
        monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun',
        'Jul','Aoû','Sep','Oct','Nov','Déc'],
        monthStatus: 'Voir un autre mois', yearStatus: 'Voir un autre année',
        weekHeader: 'Sm', weekStatus: '',
        dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
        dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
        dayNamesMin: ['Di','Lu','Ma','Me','Je','Ve','Sa'],
        dayStatus: 'Utiliser DD comme premier jour de la semaine', dateStatus: 'Choisir le DD, MM d',
        dateFormat: 'dd/mm/yy', firstDay: 0, 
        initStatus: 'Choisir la date', isRTL: false};
    $j.datepicker.setDefaults($j.datepicker.regional['fr']);
});

/*
 * END OF CONSTANTS/GLOBAL VARS 
 */

/*
 * ********************************************************************************************************************************
 * ********************************************** Autoload ***************************************************************
 * ********************************************************************************************************************************
 */

/**
 * @name formBackUp
 * @desc Add event for all form on the page.
 *          Store automatically texts entries (inputText, textarea only) 
 *          and load automatically while the form don't submit on differents pages.
 * 
 * @param options - Object - Contains options:
 *								useURL - bool - Use the URL for store and get the values. Not used if your form must be called on injected on several pages. [false]
 *								datasPersistent - bool - If true, don't reset datas after the form submit. [false]
 *								forceId - string - If send, use the string sended as name of form. Useful for use data of a other form. But the input/textarea must have the same ID ! [false]
 * 
 * @author Dhenain Ambroise
 * @date 03/11/2012
 * @lastUpdate 04/11/2012
 */
$j(function(){
    // formBackUp sert à sauvegarder automatiquement les informations des formulaires dans le navigateur le temps que le formulaire soit envoyé.
    $j.fn.formBackUp = function(options){
		
		if(!localStorage){
			console.log("LocalStorage n'est pas activé sur ce navigateur. formBackUp désactivé.");
			return false;
		}

		// Check options param.
		options = (options)? options : new Object();
		options.useURL = (options.useURL)? options.useURL : false;
		options.datasPersistent = (options.datasPersistent)? options.datasPersistent : false;
		options.forceId = (options.forceId)? options.forceId : false;
		
		// Get url.
		var url = (options.useURL)? document.location.host+document.location.pathname+'-' : '' ;

		var formDatas = {};
		var formLocalStorage = false;
		var namespace = 'formBackUp';
		var currentFormId = (options.forceId)? options.forceId : $j(this).attr('id');
		var keyLocalStorage = url+namespace+'-'+currentFormId;

		// The form will be references by this ID. Don't continue if it don't have.
		if(currentFormId != undefined){
			formDatas.id = currentFormId;

			// Get datas from the localStorage.
			if(localStorage[keyLocalStorage]){
				formLocalStorage = JSON.parse(localStorage[keyLocalStorage]);
				if(formLocalStorage.id == formDatas.id){
					for(var key in formLocalStorage){
						// Update the input value with the latest value stored in the localStorage.
						$j('#'+currentFormId+' #'+key).val(formLocalStorage[key]);
				
						// Store also the value to the formDatas for use it after.
						formDatas[key] = formLocalStorage[key];
					}
				}
			}
			// Save in realtime all values in keyup event for input and textarea.
			$j('#'+currentFormId).find('input, textarea').keyup(function(e){
				// When key is up. (realtime)
				formDatas[$j(this).attr('id')] = $j(this).val();
				localStorage.setItem(keyLocalStorage, JSON.stringify(formDatas));
			}).change(function(e){
				// When value is changed (if the browser display list of choice, it's mandatory for save the choice !
				formDatas[$j(this).attr('id')] = $j(this).val();
				localStorage.setItem(keyLocalStorage, JSON.stringify(formDatas));
			});

			// Lors d'un submit on supprime le contenu conservé dans le localStorage.
			if(!options.datasPersistent){
				$j('#'+currentFormId).submit(function(e){
					localStorage.removeItem(keyLocalStorage);
				});
			}
		}else{
			console.log("FormBackUp a été désactivé pour le formulaire suivant car il ne possède pas d'ID:");
			console.log($j(this));
		}
	}
});

/*
 * ********************************************************************************************************************************
 * ********************************************** General functionalities ***************************************************************
 * ********************************************************************************************************************************
 */

/**
 * @name ajax
 * @desc Execute a AJAX request with the $j.ajax() library. Parse response. Display eventuals errors.
 * 		Call the callback function passed.
 * 
 * @example Check user login.
 			ajax(site_url(config.controller, 'checkLoginAjax'), datas, false, function(response){
 				console.log(response.message);

 			}, 'GET');
 * 
 * @param url - string - Url to call.
 * @param datas - mixed - Datas, can be string or array. If array, convert to string.
 * @param elementToDisplayError - mixed - If 'statut' response is FALSE, if it false, no display error and call th callback function even if error. Else it's the name of the element to display the error. [FALSE]
 * @param callback - function - Function called if the response is correctly parsed. 
 * @param method - string - Method used for call url. Choice inter GET/POST/DELETE/UPDATE. [POST]
 * 
 * @author Dhenain Ambroise
 * @date 12/06/2012
 */
function ajax(url, datas, elementToDisplayError, callback, method){
    // Default method is POST.
    if(!method){
            method = 'POST';
    }

    // If datas is an array, convert to string.
    if(is_array(datas)){
            var datas_temp = '';
            for ( var data in datas) {
                    datas_temp += data+'='+datas[data]+'&';
            }
            datas = datas_temp;

            // Add the required ajax data.
            datas+= "ajax=1";
    }else{
            datas+= "&ajax=1";
    }

    if(!elementToDisplayError){
            elementToDisplayError = false;
    }

    // Call with jQuery ajax.
    $j.ajax({
            type: method,// POST/GET/PUT/DELETE
            url: url,// The URI to call. Use site_url() for generate this.
            data: datas,// Data, format: 'var1=x&var2=y&...'
            dataType: 'text',// Data type (text/xml/json...
            success: function(response){
                    var responseOk = true;
                    var error = '';// Used only if the parsing bug.

                    consoleDevelopment(response);

                    // On vérifie que la chaine retournée ne soit pas nulle avant de la parser en JSON.
                    // Check if the string is not null before parsing. (Else, fatal error...)
                    if(response.length != 0){
                            // Try to parse the response because maybe the response format is not correctly and fatal error.
                            try{
                                    response = $j.parseJSON(response);
                            }catch(e){
                                    responseOk = false;
                                    error = e.message;
                            }

                            // Check if the response is correctly parsed.
                            if(responseOk){
                                    if(response.statut == false){
                                            // Error in the response, but it's a 
                                            if(!elementToDisplayError){
                                                    // Don't display error here, call the callback function.
                                                    callback(response);
                                            }else{
                                                    // Display the error
                                                    consoleDevelopment('Une erreur est survenue lors du parsage de la réponse ajax, message d\'erreur jQuery:');
                                                    consoleDevelopment(error);
                                                    consoleDevelopment('Contenu de la réponse ajax:');
                                                    consoleDevelopment(response);

                                                    // Auto add '#' if no one symbol exists.
                                                    elementToDisplayError.firstChar = elementToDisplayError.substring(0, 1);
                                                    if(elementToDisplayError.firstChar != '#' && elementToDisplayError.firstChar != '.'){
                                                            elementToDisplayError = '#'+elementToDisplayError;
                                                    }

                                                    // Update CSS class to the element who display message and show it.
                                                    $j(elementToDisplayError).removeClass('alert alert-block alert-success alert-error alert-info').addClass(config['alert'][type]).show();

                                                    // Display the error and auto hide.
                                                    $j(elementToDisplayError).html(response.message);
                                                    hideMessage(elementToDisplayError, 3000);
                                            }
                                    }else{
                                            // No error. Call the callback function.
                                            callback(response);
                                    }									
                            }else{
                                    // Display the error
                                    consoleDevelopment('Une erreur est survenue lors du parsage de la réponse ajax, message d\'erreur jQuery:');
                                    consoleDevelopment(error);
                                    consoleDevelopment('Contenu de la réponse ajax:');
                                    consoleDevelopment(response);

                                    // Display the error message.
                                    displayMessage(response.message);
                            }
                    }else if(response.length == 0){
                            // If the string is empty, return error in JS array.
                            callback($j.parseJSON(
                                            '{'+
                                            '"message": "Réponse vide.",'+
                                            '"statut": false'+
                                            '}'
                                    )
                            );
                    }
            }
    });
}



/**
 * @name webService
 * @desc Execute a AJAX request with the $j.ajax() library. Parse response. Display eventuals errors.
 * 		Call the callback function passed. Only used for call the Carwego WebService.
 * 
 * @example Get all contacts.
 			webService('contact', 'contacts', 'get', '', false, function(response){
 				console.log(response);
			});
 * 
 * @param controller - string - Controller to call.
 * @param methodCalled - string - Method of the controller called. [Default: Value of controller]
 * @param methodUsed - string - Method used for call url. Choice inter GET/POST/DELETE/UPDATE. [GET]
 * @param datas - mixed - Datas, can be string or array. Depends on method used. [Default: Empty]
 * @param elementToDisplayError - mixed - If 'statut' response is FALSE, if it false, no display error and call th callback function even if error. Else it's the name of the element to display the error. [FALSE]
 * @param callback - function - Function called if the response is correctly parsed. 
 * @param format - string - Format to return the datas by the WebService. Choice inter XML/JSON/CSV/PHP/SERIALIZE/HTML [JSON]
 * @param login - string - Login for initialize the WebService connection.
 * @param password - string - Password for initialize the WebService connection.
 * 
 * @author Dhenain Ambroise
 * @date 10/07/2012
 * @lastUpdate 11/07/2012
 */
function webService(controller, methodCalled, methodUsed, datas, elementToDisplayError, callback, format, login, password){
    // To lower the first char of the controller
    controller = lcfirst(controller);

    // To lower the first char of the methodCalled.
    methodCalled = lcfirst(methodCalled);

    // To lower the first char of the methodUsed.
    methodUsed = lcfirst(methodUsed);

    if(!datas){
            datas = '';
    }

    // Default methodCalled is the value of the controller called.
    if(!methodCalled || methodCalled == ''){
            methodCalled = controller;
    }

    // Default method is GET.
    if(!methodUsed){
            methodUsed = 'get';
    }

    // Default format is JSON.
    if(!format){
            format = 'json';
    }

    if(!elementToDisplayError){
            elementToDisplayError = false;
    }

    // Generate the url.
    var url = webservice_site_url(controller, format, methodCalled, login, password);

    if(methodUsed == 'get'){
            // If is not a string but an array of params.
            if(is_array(datas)){
                    var datas_temp = '';
                    for ( var data in datas) {
                            datas_temp += data+'/'+datas[data]+'/';
                    }
                    datas = datas_temp;
            }

            // Add datas to the controller if GET. (http://localhost:8080/CRM/api/reservationControllerWebService/reservation/id_maker/4/id_provider/1/format/json)
            url+= datas;
            datas = "";// Reset datas.
    }


    // Call with jQuery ajax.
    $j.ajax({
            type: methodUsed,// POST/GET/PUT/DELETE
            url: url,// The URI to call. Use site_url() for generate this.
            data: datas,// Array of values.
            dataType: 'text',// Data type (text/xml/json...
            success: function(response){
                    var responseOk = true;
                    var error = '';// Used only if the parsing bug.

                    // On vérifie que la chaine retournée ne soit pas nulle avant de la parser en JSON.
                    // Check if the string is not null before parsing. (Else, fatal error...)
                    if(response.length != 0){
                            // Try to parse the response because maybe the response format is not correctly and fatal error.
                            try{
                                    response = $j.parseJSON(response);
                            }catch(e){
                                    responseOk = false;
                                    error = e.message;
                            }

                            // Check if the response is correctly parsed.
                            if(responseOk){
                                    if(response.statut == false){
                                            // Error in the response, but it's a 
                                            if(!elementToDisplayError){
                                                    consoleDevelopment(response);
                                                    // Don't display error here, call the callback function.
                                                    callback(response);
                                            }else{
                                                    // Display the error
                                                    consoleDevelopment('Une erreur est survenue lors du parsage de la réponse du WebService, message d\'erreur jQuery:');
                                                    consoleDevelopment(error);
                                                    consoleDevelopment('Contenu de la réponse du WebService:');
                                                    consoleDevelopment(response);

                                                    // Auto add '#' if no one symbol exists.
                                                    elementToDisplayError.firstChar = elementToDisplayError.substring(0, 1);
                                                    if(elementToDisplayError.firstChar != '#' && elementToDisplayError.firstChar != '.'){
                                                            elementToDisplayError = '#'+elementToDisplayError;
                                                    }

                                                    // Update CSS class to the element who display message and show it.
                                                    $j(elementToDisplayError).removeClass('alert alert-block alert-success alert-error alert-info').addClass(config['alert'][type]).show();

                                                    // Display the error and auto hide.
                                                    $j(elementToDisplayError).html(response.message);
                                                    hideMessage(elementToDisplayError, 3000);
                                            }
                                    }else{
                                            // No error. Call the callback function.
                                            consoleDevelopment(response);
                                            callback(response);
                                    }									
                            }else{
                                    // Display the error
                                    consoleDevelopment('Une erreur est survenue lors du parsage de la réponse du WebService, message d\'erreur jQuery:');
                                    consoleDevelopment(error);
                                    consoleDevelopment('Contenu de la réponse du WebService:');
                                    consoleDevelopment(response);

                                    // Display the error message.
                                    displayMessage(response.message);
                            }
                    }else if(response.length == 0){
                            // If the string is empty, return error in JS array.
                            callback($j.parseJSON(
                                            '{'+
                                            '"message": "Réponse vide.",'+
                                            '"statut": false'+
                                            '}'
                                    )
                            );
                    }
            }
    });
}

/**
 * @name checkWebServiceResponse
 * @desc Check if the request status is true and display error else.
 * 
 * @param mixed - required - response - The WebService response.
 * 
 * @return True/False.
 * 
 * @author Dhenain Ambroise
 * @date 10/07/2012
 */
function checkWebServiceResponse(response){
    if(!response || response == ''){
            displayMessage("La requête au WebService n'a retourné aucun résultat.", 'warning');
            return false;
    }

    // Check request is done.
    if(!response.status){
            // Error.	  
            if(is_array(response.errors) && response.errors.length > 0){
                    for(var i = 0; i < response.errors.length; i++){
                            displayMessage(response['errors'][i]['message'], (response['errors'][i]['critical'] == 'Fatal error')? 'error': 'warning', true, '', 0, false);
                    } 
            }else{
                    displayMessage('Une erreur est survenue lors de la requête au WebService.', 'error', true, '', 0, false);
                    consoleDevelopment(response);
            }
            return false;
    }else{
            return true;
    }
}

/**
 * @name convertWebServiceDatasToArray
 * @desc Convert datas getted by the webservice in array. Use for DATATABLE specifically.
 * 
 * @param mixed - required - response - The WebService response.
 * @param int - required - nbResults - Number of results to convert.
 * @param array - required - fieldsToGet - The array of fields to get in the response.
 * 
 * @return True/False.
 * 
 * @author Dhenain Ambroise
 * @date 10/07/2012
 */
function convertWebServiceDatasToArray(datasToConvert, nbResults, fieldsToGet){
    // Array for store results treated.
    var datas = new Array();

    if(!nbResults || nbResults == 0 || nbResults == false){
            nbResults = -1;// Offset start at -1.
            // Count manual of results.
            for(data in datasToConvert){
                    nbResults++;
            }
    }

    for(var i = 0; i < nbResults; i++){
            datas[i] = new Array(
                            datasToConvert[i][fieldsToGet[0]], 
                            datasToConvert[i][fieldsToGet[1]],
                            datasToConvert[i][fieldsToGet[2]],
                            datasToConvert[i][fieldsToGet[3]],
                            datasToConvert[i][fieldsToGet[4]]
            );
    }

    return datas;
}

/**
 * @name site_url
 * @desc Return the url based on the config_base url. [Same name of the PHP method]
 * 
 * @example site_url(config.controller, 'checkLoginAjax')
 * 
 * @param string - required - controller - The controller who will be instantiated.
 * @param string - optionnal - method - The method who will be called. [index]
 * 
 * @return The path to the method.
 * 
 * @author Dhenain Ambroise
 * @date 12/06/2012
 */
function site_url(controller, method){
    if(!method){
            method = 'index';
    }

    return config.baseUrl+controller+'/'+method;
}

/**
 * @name webservice_site_url
 * @desc Create a local URL for WebService based on your basepath. 
 * 
 * @param string - required - controller - The controller who will be instantiated.
 * @param string - optionnal - format - The format to get the datas. [JSON]
 * @param string - optionnal - method - The method of the controller. [Default: Value of controller]
 * @param login - string - Login for initialize the WebService connection.
 * @param password - string - Password for initialize the WebService connection.
 * 
 * @return The path to the method.
 * 
 * @author Dhenain Ambroise
 * @date 12/06/2012
 * @lastUpdate 11/07/2012
 */
function webservice_site_url(controller, format, method, login, password){	
    if(!method || method == ''){
            method = controller;
    }

    if(!format || format == ''){
            format = 'json';
    }

    var basePath = config.api_directory;

    if(!login){
            login = '';
    }

    if(!password){
            password = '';
    }

    if(login != '' && password != ''){
            basePath = replaceString(basePath, 'http://', 'http://'+login+':'+password+'@');
    }

    return basePath+controller+config.controller_webservice_name+'/'+method+'/'+config.rest_key_name+'/'+config.webservice_carwego_key+'/format/'+format+'/';
}

/**
 * @name consoleDevelopment
 * @desc Check if the current application is in development and if is true log the message in the developer console.
 * 
 * @param message - string - Message to display in the console.
 * 
 * @author Dhenain Ambroise
 * @date 12/06/2012
 */
function consoleDevelopment(message){
    if(config.environment == 'development'){
            console.log(message);
    }
}

/**
 * @name displayMessage
 * @desc Display an error in the div globalError.
 * 
 * @param message - string - required - Message to display
 * @param type - string - optionnal - Type of the message to display. (error/warning/success/info) Defined the css class used for the div. [error]
 * @param appendPreviousMessage - bool - optionnal - If true, append the current message to the previous message. [false]
 * @param elementToDisplay - string - optionnal - The name of the element who display the message. [#globalError]
 * @param timeBeforeHide - int - optionnal - If autoHide, this is the time before the message will be hidden. [3000]
 * @param dontHide - bool - optionnal - If false, the message will be automatically hidden. [false]
 * 
 * @return The path to the view.
 * 
 * @author Dhenain Ambroise
 * @date 12/06/2012
 * @lastUpdate 21/11/2012
 */
function displayMessage(message, type, appendPreviousMessage, elementToDisplay, timeBeforeHide, dontHide){
    if(!type){
            type = "error";
    }

    if(type == 'warning'){
            type = 'block';
    }

    if(!appendPreviousMessage){
            appendPreviousMessage = false;
    }

    if(!elementToDisplay || elementToDisplay == ''){
            elementToDisplay = '#globalError';
    }else{
            // Auto add '#' if no one symbol exists.
            var firstChar = elementToDisplay.substring(0, 1);

            if(firstChar != '#' && firstChar != '.'){
                    elementToDisplay = '#'+elementToDisplay;
            }
    }

    if(!dontHide){
            dontHide = false;
    }

    if(!timeBeforeHide && !dontHide){
            timeBeforeHide = 3000;
    }

    if(appendPreviousMessage){
            // Display message.
            $j(elementToDisplay).append(message);
    }else{
		// Display message.
		message = "<a class='close' onclick='$j(\""+elementToDisplay+"\").css(\"display\", \"none\");'>×</a>" + message;
		$j(elementToDisplay).html(message);
    }

    // Update CSS class to the element who display message and show it.
    $j(elementToDisplay).removeClass().addClass(config['alert'][type]).show();	

    if(!dontHide){
            hideMessage(elementToDisplay, timeBeforeHide);
    }	
}

/**
 * @name hideMessage
 * @desc Hide a message container.
 * 
 * @param elementToHide - string - Element to hide. [#globalError]
 * @param time - int - Delay before the function call recursively. [0]
 * 
 * @author Dhenain Ambroise
 * @date 12/06/2012
 * @lastUpdate 12/06/2012
 */
function hideMessage(elementToHide, time){
    if(!elementToHide || elementToHide == ''){
        elementToHide = '#globalError';
    }else{
            // Auto add '#' if no one symbol exists.
            var firstChar = elementToHide.substring(0, 1);

            if(firstChar != '#' && firstChar != '.'){
                    elementToHide = '#'+elementToHide;
            }
    }
    
    if(!time){
        time = false;
    }
    
    if(time){
        clearTimeout(hideMessage_setTimeOut);
        hideMessage_setTimeOut = setTimeout("hideMessage('"+elementToHide+"')", time);
    }else{
        $j(elementToHide).hide();
    }
}

/**
 * @name applyPagination
 * @desc Apply a pagination, use AJAX for get the datas.
 * 
 * @param elementToAddClick - string - Element to add the click event (usually, the pagination div).
 * @param elementToAddResults - string - Element to add the results. (usually, a div)
 * @param datas - string - Datas to add for the AJAX request.
 * 
 * @author Dhenain Ambroise
 * @date 02/11/2012
 * @lastUpdate 07/11/2012
 */
function applyPagination(elementToAddClick, elementToAddResults, datas) {
	if(!datas){
		datas = '';
	}
    $j(elementToAddClick).click(function() {
        $j('#spanAjaxLoader').show();
        var url = $j(this).attr("href");
        $j.ajax({
                type: "POST",
                data: "ajax=1&"+datas,
                url: url,
                success: function(msg) {
                        $j(elementToAddResults).html(msg);
                        applyPagination(elementToAddClick, elementToAddResults, datas);
                        $j('#spanAjaxLoader').hide();
                }
      });
      return false;
    });
}

/**
 * @name setLocalStorage
 * @desc Save a data in the localStorage, referenced by the key.
 * 
 * @param key - string - Key, used for reference the value.
 * @param value - object - Value. Stringify.
 * 
 * @author Dhenain Ambroise
 * @date 02/11/2012
 * @lastUpdate 02/11/2012
 */
function setLocalStorage(key, value){
    localStorage.setItem(key, JSON.stringify(value));
}

/**
 * @name getLocalStorage
 * @desc Load a data in te localStorage by a key.
 * 
 * @param key - string - Key's element to get.
 * @param defaultValueUndefined - mixed - The default value to return if the key don't exists.
 * 
 * @return mixed - The value. [undefined if the key don't exists]
 * 
 * @author Dhenain Ambroise
 * @date 02/11/2012
 * @lastUpdate 02/11/2012
 */
function getLocalStorage(key, defaultValueUndefined){
    return (JSON.parse(localStorage.getItem(key)))? JSON.parse(localStorage.getItem(key)) : (defaultValueUndefined)? defaultValueUndefined : undefined;
}

/**
 * @name detectActions
 * @desc Detec all actions to lauch automatically when the page is loaded.
 * 
 * @author Dhenain Ambroise
 * @date 06/11/2012
 * @lastUpdate 06/11/2012
 */
function detectActions(){
	var arrayActions = getLocationHash();	
	for(var i = 0; i < arrayActions.length; i++){
		if(arrayActions[i].length > 0){
			switch(arrayActions[i]){
				case globalFormContactHash:
					displayContactForm();
					consoleDevelopment("Une action automatique a été exécutée: Formulaire de contact.");
					break;
				// AutoSelect
				case 'formations':
				case 'traineeships':
				case 'projects':
				case 'jobs':
					autoSelectMenuExperience(arrayActions[i]);
					break;
				default:
					consoleDevelopment("Une action indéterminée a été détectée mais non exécutée: "+arrayActions[i]);
					break;
			}
		}
	}
}

/**
 * @name addLocationHash
 * @desc Add a hash for the current hash. Can replace or add the hash provide at the latest hash.
 * 
 * @author Dhenain Ambroise
 * @date 09/11/2012
 * @lastUpdate 09/11/2012
 */
function addLocationHash(hash, replace){
	replace = (replace)? replace : false ;
	
	if(replace){
		location.hash = hash;
	}else{
		// If already exists, delete this.
		location.hash = replaceString(location.hash, '|'+hash, '')
		location.hash = replaceString(location.hash, hash, '')
		
		// Add the hash.
		if(location.hash.length > 0){
			location.hash+= '|'+hash;
		}else{
			location.hash = hash;
		}
		
	}
}

/**
 * @name removeLocationHash
 * @desc Remove in the location.hash the hash provided.
 * 
 * @author Dhenain Ambroise
 * @date 09/11/2012
 * @lastUpdate 09/11/2012
 */
function removeLocationHash(hash){
	// Try to delete hash+separator.
	location.hash = replaceString(location.hash, '|'+hash, '');
	// If there is no separator, the last command don't works, so try again, without the separator.
	location.hash = replaceString(location.hash, hash, '');
}

/**
 * @name getLocationHash
 * @desc Return an array of location or only one location. If not location founded, return false.
 * 
 * @return mixed (array, string, bool)
 * 
 * @author Dhenain Ambroise
 * @date 09/11/2012
 * @lastUpdate 10/11/2012
 */
function getLocationHash(index){
	// False by default.
	index = (index)? index : false;
	
	if(location.hash != ''){
		var arrayActions = replaceString(location.hash, '#', '').split('|');

		// If array is not empty and index is valid, return only the content from the index needed. Else, if the array is not empty, return the entire array. Else false.
		return (index && index >= 0 && arrayActions.length)? arrayActions[index] : (arrayActions.length)? arrayActions : false ;
	}else{
		return false;
	}
}

/**
 * @name autoFillForm
 * @desc Auto fill a form for the fields specified. Fill with the localstorage values.
 * 
 * @param elementParent - string - The element parent. Usually, the form id.
 * @param fields - Object - Key: The id of the input to fill.
 *							Value: The key of the localstorage value to get.
 * 
 * @author Dhenain Ambroise
 * @date 11/11/2012
 * @lastUpdate 11/11/2012
 */
function autoFillForm(elementParent, fields){
	for(var field in fields){
		var value = getLocalStorage(fields[field], undefined);
		var valueInput = $j(elementParent+' #'+field).val();
		
		// If the value is not undefined and the input is empty.
		(value != undefined && valueInput != undefined && valueInput.length == 0)? $j(elementParent+' #'+field).val(value) : '' ;
	}
}

/**
 * @name getSubmitButtonByForm
 * @desc Return the submit button for the form.
 * 
 * @param form - string - The form ID.
 * 
 * @return The submit button element.
 * 
 * @author Dhenain Ambroise
 * @date 01/12/2012
 * @lastUpdate 01/12/2012
 */
function getSubmitButtonByForm(form){
	return $j(form).children('input[type=submit]');
}


/*
 * ********************************************************************************************************************************
 * ********************************************** Others functionalities ***************************************************************
 * ********************************************************************************************************************************
 */

/**
 * @name trim
 * @desc Trim a string. As the trim method PHP.
 * 
 * @param message - string - Message to trim.
 * 
 * @author Dhenain Ambroise
 * 
 * @date: 12/06/2012
 * @lastUpdate 12/06/2012
 */
function trim(str) {
    str = str.replace(/^\s+/, '');
    for (var i = str.length - 1; i >= 0; i--) {
            if (/\S/.test(str.charAt(i))) {
                    str = str.substring(0, i + 1);
                    break;
            }
    }

    return str;
}

/**
 * @name ucfirst
 * @desc First char TO UPPER.
 * 
 * @param str - string - String.
 * 
 * @author Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 * 
 * @date: 12/06/2012
 * @lastUpdate 12/06/2012
 */
function ucfirst(str){
    str += '';
    var f = str.charAt(0).toUpperCase();
    return f + str.substr(1);
}

/**
 * @name lcfirst
 * @desc First char TO LOWER.
 * 
 * @param str - string - String.
 * 
 * @author Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 * 
 * @date: 12/06/2012
 * @lastUpdate 12/06/2012
 */
function lcfirst(str){
    str += '';
    var f = str.charAt(0).toLowerCase();
    return f + str.substr(1);
}

/**
 * @Author: Dhenain Ambroise
 * @Name: getCookie
 * @Desc: Lis un cookie.
 *
 * @date: 15/01/2012
 * @lastUpdate 15/01/2012
 *
 */
function getCookie(sName) {
    var cookContent = document.cookie, cookEnd, i, j;
    var sName = sName + "=";

    for (i=0, c=cookContent.length; i<c; i++) {
            j = i + sName.length;
            if (cookContent.substring(i, j) == sName) {
                    cookEnd = cookContent.indexOf(";", j);
                    if (cookEnd == -1) {
                            cookEnd = cookContent.length;
                    }
                    return decodeURIComponent(cookContent.substring(j, cookEnd));
            }
    }       
    return null;
}

/**
 * @Author: Dhenain Ambroise
 * @Name: setCookie
 * @Desc: écris un cookie.
 *
 * @date: 15/01/2012
 * @lastUpdate 15/01/2012
 *
 */
function setCookie (nom,valeur,expire,path,domaine,securise) {
    consoleDevelopment('Création du cookie <'+nom+'>:<'+valeur+'>\nCookie détruit le :'+expire);
    document.cookie = nom + "=" + escape (valeur) + ((expire) ? "; expires=" + expire.toGMTString() : "") + ((path) ? "; path=" + path : "") + ((domaine) ? "; domain=" + domaine : "") + ((securise) ? "; secure" : "");
}

/**
 * @Author: Dhenain Ambroise
 * @Name: deleteCookie
 * @Desc: Supprime un cookie.
 *
 * @date: 15/01/2012
 * @lastUpdate 15/01/2012
 *
 */
function deleteCookie(sName, sValue) {
    if(!sValue){
            sValue = "";
    }
    var today = new Date(), expires = new Date();
    expires.setTime(today.getTime() - (365*24*60*60*1000));
    document.cookie = sName + "=" + encodeURIComponent(sValue) + ";expires=" + expires.toGMTString();
}

/**
 * @Author: Dhenain Ambroise
 * @Name: countSubString
 * @Desc: Compte le nombre d'occurence de la chaine voulue dans la chaine cherchée.
 *
 * @Param	chaine - Chaine dans laquelle on recherche.
 * 			chaineATrouver - Chaine que l'on recherche.
 * 
 * @Return nbResult - Nombre d'occurences trouvées de chaineATrouver dans chaine.
 * 
 * @date: 01/04/2012
 * @lastUpdate 01/04/2012
 *
 */
function countSubString(chaine, chaineATrouver){
    var nbResult = 0; 
    for (var i = 0; i < chaine.length;i++) { 
            if (chaineATrouver == chaine.substr(i, chaineATrouver.length)) 
                    nbResult++; 
    } 
    return nbResult; 
}

/**
 * @Author: Dhenain Ambroise
 * @Name: inArray
 * @Desc: Cherche si array contient val.
 *
 * @Param	array - Tableau dans lequel on recherche.
 * 			val - Valeur que l'on recherche.
 * 
 * @Return bool - True si array contient val, false si non.
 * 
 * @date: 15/09/2012
 * @lastUpdate 15/09/2012
 *
 */
function inArray(array, val) {
    var l = array.length;
    for(var i = 0; i < l; i++) {
        if(array[i] == val) {
            return true;
        }
    }
    return false;
}

/**
 * @Author: toutjavascript.com
 * @Name: replaceString
 * @Desc: Recherche dans une chaîne expr toutes les occurrences d'une sous-chaîne a et les remplace par une sous-chaîne b.
 *
 * @Param 	expr - Chaine dans laquelle effectuer la recherche et le remplacement.
 * 			a - Chaine que l'on recherche.
 * 			b - Chaine par laquelle on remplace les occurences trouvées.
 * 
 * @Return expr - Chaine modifiée.
 * 
 * @date: 01/04/2012
 * @lastUpdate 01/04/2012
 *
 */
function replaceString(expr, a, b) {
    var i = 0;
    while(i != -1){
            i = expr.indexOf(a, i);
            if(i >= 0){
                    expr = expr.substring(0, i) + b + expr.substring(i + a.length);
                    i+= b.length;
            }
    }
    return expr;
}

/**
 * @Author: Dhenain Ambroise
 * @Name: checkEmail
 * @Desc: Vérifie qu'un email est valide.
 *
 * @date: 29/01/2012
 * @lastUpdate 29/01/2012
 *
 */
function checkEmail(email){
    nb_erreur = 0;		
    var reg = new RegExp('^[a-z0-9]+([_|\.|-]{1}[a-z0-9]+)*@[a-z0-9]+([_|\.|-]{1}[a-z0-9]+)*[\.]{1}[a-z]{2,6}$', 'i');

    var email_valide = reg.test(email);

    if(!email_valide){
            nb_erreur++;
    }

    if(nb_erreur == 0){
            return true;
    }else{
            return false;
    }
}


/**
 * @Author: Dhenain Ambroise
 * @Name: checkPhone
 * @Desc: Vérifie qu'un email est valide.
 *
 * @date: 29/01/2012
 * @lastUpdate 29/01/2012
 *
 */
function checkPhone(tel){
    var regexpTel = new RegExp("^(0[1-68])(?:[ _.-]?(\\d{2})){4}$");

    if(regexpTel.test(tel)){
            return true;
    }else{
            return false;
    }
}

/**
 * @author Dhenain Ambroise
 * @name is_array
 * @desc Check if the input is an array. http://phpjs.org/functions/is_array:437
 * 			[Je comprends absolument pas grand chose à ce qui est fait. Fat le mec qui l'a codé.]
 * 
 * @param mixed_var - mixed - Value to check.
 * 
 * @date: 12/06/2012
 * @lastUpdate 12/06/2012
 */
function is_array (mixed_var) {
    var ini,
    _getFuncName = function (fn) {
            var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
            if (!name) {
                    return '(Anonymous)';
            }
            return name[1];
    },
    _isArray = function (mixed_var) {
            // return Object.prototype.toString.call(mixed_var) === '[object Array]';
            // The above works, but let's do the even more stringent approach: (since Object.prototype.toString could be overridden)
            // Null, Not an object, no length property so couldn't be an Array (or String)
            if (!mixed_var || typeof mixed_var !== 'object' || typeof mixed_var.length !== 'number') {
                    return false;
            }
            var len = mixed_var.length;
            mixed_var[mixed_var.length] = 'bogus';
            // The only way I can think of to get around this (or where there would be trouble) would be to have an object defined 
            // with a custom "length" getter which changed behavior on each call (or a setter to mess up the following below) or a custom 
            // setter for numeric properties, but even that would need to listen for specific indexes; but there should be no false negatives 
            // and such a false positive would need to rely on later JavaScript innovations like __defineSetter__
            if (len !== mixed_var.length) { // We know it's an array since length auto-changed with the addition of a 
                    // numeric property at its length end, so safely get rid of our bogus element
                    mixed_var.length -= 1;
                    return true;
            }
            // Get rid of the property we added onto a non-array object; only possible 
            // side-effect is if the user adds back the property later, it will iterate 
            // this property in the older order placement in IE (an order which should not 
            // be depended on anyways)
            delete mixed_var[mixed_var.length];
            return false;
    };

    if (!mixed_var || typeof mixed_var !== 'object') {
            return false;
    }

    // BEGIN REDUNDANT
    this.php_js = this.php_js || {};
    this.php_js.ini = this.php_js.ini || {};
    // END REDUNDANT

    ini = this.php_js.ini['phpjs.objectsAsArrays'];

    return _isArray(mixed_var) ||
    // Allow returning true unless user has called
    // ini_set('phpjs.objectsAsArrays', 0) to disallow objects as arrays
    ((!ini || ( // if it's not set to 0 and it's not 'off', check for objects as arrays
                    (parseInt(ini.local_value, 10) !== 0 && (!ini.local_value.toLowerCase || ini.local_value.toLowerCase() !== 'off')))
    ) && (
                    Object.prototype.toString.call(mixed_var) === '[object Object]' && _getFuncName(mixed_var.constructor) === 'Object' // Most likely a literal and intended as assoc. array
    ));
}

/**
 * @Author: Dhenain Ambroise
 * @Name: transformCheckboxToTriStateCheckbox
 * @Desc: Transform a checkbox with two state (true/false) on checkbox with three states (true/false/null). 
 * 			http://dotnetdawson.wordpress.com/2010/12/05/three-state-html-checkboxes/
 *
 * @date: 19/07/2012
 * @lastUpdate 19/07/2012
 */
function transformCheckboxToTriStateCheckbox(id){
    $j(id).each(function () {
        // keep a reference to the current checkbox
        var me = $j(this);
        // define the three states our checkbox can enter:
        // checked, not checked, and faded out
        var states = [
            function () {
                me.val(true);
                me.attr('checked', 'checked');
                me.css({ opacity: 1 });
            },
            function () {
                me.val(false);
                me.removeAttr('checked');
                me.css({ opacity: 1 });
            },
            function () {
                me.val('null');
                me.removeAttr('checked');
                me.css({ opacity: 0.2 });
            }
        ];
        // start off in the not checked state
        var currentState = 1;
        // whenever the checkbox is changed, loop through our checkbox states
        me.change(function () {
            states[++currentState > 2 ? currentState = 0 : currentState]();
        });
        // force it into the next state:
        // I want it to start off in the faded out state (currentState == 2)
        me.change();
    });
}


/**
 * @Author: Dhenain Ambroise
 * @Name: checkPhone
 * @Desc: Vérifie qu'un email est valide.
 *
 * @param page
 * @param time
 * 
 * @date: 19/06/2012
 * @lastUpdate 19/06/2012
 */
function loadPage(page, time){
    if(time){
        clearTimeout(loadPage_setTimeOut);
        loadPage_setTimeOut = setTimeout("loadPage('"+page+"')", time);
    }else{
        $j(location).attr('href', page);
    }
}

/**
 * @Author: Dhenain Ambroise
 * @Name: detectEnterKeyPressed
 * @Desc: Returne true si la clé passée est la clé générée par l'appui de la touche entrée.
 *
 * @param key - int - The key to check.
 * 
 * @date: 23/07/2012
 * @lastUpdate 23/07/2012
 */
function detectEnterKeyPressed(key){
    if(key == 13){
        return true;
    }else{
        return false;
    }
}

/**
 * @Author: Dhenain Ambroise
 * @Name: cloneObject
 * @Desc: To copy or clone an independant array/object.
 *
 * @param source - object - The object to clone.
 * 
 * @date: 23/07/2012
 * @lastUpdate 23/07/2012
 */
function cloneObject(source) {
    for (i in source) {
        if (typeof source[i] == 'source') {
            this[i] = new cloneObject(source[i]);
        }
        else{
            this[i] = source[i];
	}
    }
}

/**
 * @Author: Dhenain Ambroise
 * @Name: autoDetectDivToUseJTable
 * @Desc: Detect automatically the div to return. Manage two page for the moment, reservationController/index and contactController/index.
 * 			Detect predefined div and returns this if exists.
 * 
 * @date: 23/07/2012
 * @lastUpdate 23/07/2012
 */
function autoDetectDivToUseJTable(){
    if($j('#divTableReservations').length > 0){
        return 'divTableReservations';
    }else if($j('#divTableContacts').length > 0){
        return 'divTableContacts';
    }
}

/**
 * @Author: Dhenain Ambroise
 * @Name: getTrueUrl
 * @Desc: Check if the current url is only with controller (without /index or /method)
 * 			Return the true url of the controller/method passed in controller/method.
 * 
 * @param controller - string - The controller (the class).
 * @param method - string - The method.
 * 
 * @date: 29/10/2012
 * @lastUpdate 29/10/2012
 */
function getTrueUrl(controller, method){
    
    // Typically: /Vadorequest.fr/administration/index
    var currentLocation = location.pathname;
    /*
     * 0: empty
     * 1: Vadorequest.fr
     * 2: Controller
     * 3: Class
     * 4+: Params
     */
    var locations = currentLocation.split('/');
    
    if(locations[2] != undefined){
        var currentController = locations[2];
        if(locations[3] != undefined){
            var currentMethod = locations[3];
        }else{
            var currentMethod = undefined;
        }
    }
    
    // If the current url undefined the method (so, it's 'index' but it's not displayed)
    if(currentMethod == undefined){
        return config.baseUrl+controller+'/'+method;
    }else{
        // If the controller equal the current controller, return just the method name.
        if(controller == currentController){
            return method;
        }else{
            // Else, need to return the entire path.
            return config.baseUrl+controller+'/'+method;
        }	
    }
}

/**
 * @Author: superpigeon - http://forum.alsacreations.com/topic-5-31802-1-Recuperer-un-parametre-durl-en-javascript.html
 * @Name: extractUrlParams
 * @Desc: Fonction de récupération des paramètres GET de la page
 * 
 * @return Array - Tableau associatif contenant les paramètres GET
 * 
 * @date: 04/11/2012
 * @lastUpdate 04/11/2012
 */
function extractUrlParams(){	
	var t = location.search.substring(1).split('&');
	var f = [];
	for (var i=0; i<t.length; i++){
		var x = t[ i ].split('=');
		f[x[0]]=x[1];
	}
	return f;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  SHA-1 implementation in JavaScript | (c) Chris Veness 2002-2010 | www.movable-type.co.uk      */
/*   - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html                             */
/*         http://csrc.nist.gov/groups/ST/toolkit/examples.html                                   */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Sha1 = {};  // Sha1 namespace

/**
 * Generates SHA-1 hash of string
 *
 * @param {String} msg                String to be hashed
 * @param {Boolean} [utf8encode=true] Encode msg as UTF-8 before generating hash
 * @returns {String}                  Hash of msg as hex character string
 */
Sha1.hash = function(msg, utf8encode) {
  utf8encode =  (typeof utf8encode == 'undefined') ? true : utf8encode;
  
  // convert string to UTF-8, as SHA only deals with byte-streams
  if (utf8encode) msg = Utf8.encode(msg);
  
  // constants [§4.2.1]
  var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
  
  // PREPROCESSING 
  
  msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
  
  // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
  var l = msg.length/4 + 2;  // length (in 32-bit integers) of msg + ‘1’ + appended length
  var N = Math.ceil(l/16);   // number of 16-integer-blocks required to hold 'l' ints
  var M = new Array(N);
  
  for (var i=0; i<N; i++) {
    M[i] = new Array(16);
    for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding
      M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) | 
        (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
    } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
  }
  // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
  // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
  // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
  M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
  M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
  
  // set initial hash value [§5.3.1]
  var H0 = 0x67452301;
  var H1 = 0xefcdab89;
  var H2 = 0x98badcfe;
  var H3 = 0x10325476;
  var H4 = 0xc3d2e1f0;
  
  // HASH COMPUTATION [§6.1.2]
  
  var W = new Array(80); var a, b, c, d, e;
  for (var i=0; i<N; i++) {
  
    // 1 - prepare message schedule 'W'
    for (var t=0;  t<16; t++) W[t] = M[i][t];
    for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
    
    // 2 - initialise five working variables a, b, c, d, e with previous hash value
    a = H0; b = H1; c = H2; d = H3; e = H4;
    
    // 3 - main loop
    for (var t=0; t<80; t++) {
      var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
      var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
      e = d;
      d = c;
      c = Sha1.ROTL(b, 30);
      b = a;
      a = T;
    }
    
    // 4 - compute the new intermediate hash value
    H0 = (H0+a) & 0xffffffff;  // note 'addition modulo 2^32'
    H1 = (H1+b) & 0xffffffff; 
    H2 = (H2+c) & 0xffffffff; 
    H3 = (H3+d) & 0xffffffff; 
    H4 = (H4+e) & 0xffffffff;
  }

  return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + 
    Sha1.toHexStr(H2) + Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
};

//
// function 'f' [§4.1.1]
//
Sha1.f = function(s, x, y, z)  {
  switch (s) {
  case 0: return (x & y) ^ (~x & z);           // Ch()
  case 1: return x ^ y ^ z;                    // Parity()
  case 2: return (x & y) ^ (x & z) ^ (y & z);  // Maj()
  case 3: return x ^ y ^ z;                    // Parity()
  }
};

//
// rotate left (circular left shift) value x by n positions [§3.2.5]
//
Sha1.ROTL = function(x, n) {
  return (x<<n) | (x>>>(32-n));
};

//
// hexadecimal representation of a number 
//   (note toString(16) is implementation-dependant, and  
//   in IE returns signed numbers when used on full words)
//
Sha1.toHexStr = function(n) {
  var s="", v;
  for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
  return s;
};


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
/*              single-byte character encoding (c) Chris Veness 2002-2010                         */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Utf8 = {};  // Utf8 namespace

/**
 * Encode multi-byte Unicode string into utf-8 multiple single-byte characters 
 * (BMP / basic multilingual plane only)
 *
 * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
 *
 * @param {String} strUni Unicode string to be encoded as UTF-8
 * @returns {String} encoded string
 */
Utf8.encode = function(strUni) {
  // use regular expressions & String.replace callback function for better efficiency 
  // than procedural approaches
  var strUtf = strUni.replace(
      /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
      function(c) { 
        var cc = c.charCodeAt(0);
        return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
    );
  strUtf = strUtf.replace(
      /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
      function(c) { 
        var cc = c.charCodeAt(0); 
        return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
    );
  return strUtf;
};

/**
 * Decode utf-8 encoded string back into multi-byte Unicode characters
 *
 * @param {String} strUtf UTF-8 string to be decoded back to Unicode
 * @returns {String} decoded string
 */
Utf8.decode = function(strUtf) {
  // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
  var strUni = strUtf.replace(
      /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f); 
        return String.fromCharCode(cc); }
    );
  strUni = strUni.replace(
      /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
        return String.fromCharCode(cc); }
    );
  return strUni;
};